package com.ruoyi.yys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.GlobalDbConfig;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.constant.ExcelStants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.yys.common.enums.ErrorStatus;
import com.ruoyi.yys.domain.ImportExportTask;
import com.ruoyi.yys.domain.ImportExportTemplate;
import com.ruoyi.yys.domain.JdbcConfig;
import com.ruoyi.yys.domain.entityBo.ImportExportParamBo;
import com.ruoyi.yys.domain.factory.OperatyUtil;
import com.ruoyi.yys.mapper.ImportExportTaskMapper;
import com.ruoyi.yys.mapper.ImportExportTemplateMapper;
import com.ruoyi.yys.mapper.JdbcConfigMapper;
import com.ruoyi.yys.service.IImportExportTaskService;
import com.ruoyi.yys.util.OssUtils;
import com.ruoyi.yys.util.ReadUtils;
import com.ruoyi.yys.util.XyUtils;
import com.ruoyi.yys.util.dbUtil.JdbcDbUtil;
import com.ruoyi.yys.util.excel.ExcelUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 导入导出任务Service业务层处理
 *
 * @author xinyin
 * @date 2022-12-07
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ImportExportTaskServiceImpl implements IImportExportTaskService {
    private static ExecutorService executorService = new ThreadPoolExecutor(
            3,
            10,
            30,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(30),
            // 拒绝： 抛出异常
            new ThreadPoolExecutor.AbortPolicy());

    private final ImportExportTaskMapper importExportTaskMapper;

    private final ImportExportTemplateMapper templateMapper;

    private final JdbcConfigMapper jdbcConfigMapper;

    private final ExcelUtils excelUtils;

    private final ReadUtils readUtils;

    /**
     * 项目运行前所要运行的方法
     */
    @PostConstruct
    public void projectRunBefore() {
        //数据库查询 关闭 字段全部转为小写
        GlobalDbConfig.setCaseInsensitive(false);
        //将状态为 等待执行和执行中的任务，加入到线程中
        List<ImportExportTask> tasks = getNotExecute();
        for (ImportExportTask task : tasks) {
            restartTask(task);
        }
    }

    /**
     * 查询导入导出任务
     *
     * @param id 导入导出任务主键
     * @return 导入导出任务
     */
    @Override
    public ImportExportTask selectImportExportTaskById(String id) {
        return importExportTaskMapper.selectImportExportTaskById(id);
    }

    /**
     * 查询导入导出任务列表
     *
     * @param importExportTask 导入导出任务
     * @return 导入导出任务
     */
    @Override
    public List<ImportExportTask> selectImportExportTaskList(ImportExportTask importExportTask) {
        return importExportTaskMapper.selectImportExportTaskList(importExportTask);
    }

    /**
     * 新增导入导出任务
     *
     * @param importExportTask 导入导出任务
     * @return 结果
     */
    @Override
    public int insertImportExportTask(ImportExportTask importExportTask) {
        importExportTask.setId(XyUtils.getId32()).setCreateBy(SecurityUtils.getUsername());
        importExportTask.setCreateTime(DateUtils.getNowDate());
        return importExportTaskMapper.insertImportExportTask(importExportTask);
    }

    /**
     * 修改导入导出任务
     *
     * @param importExportTask 导入导出任务
     * @return 结果
     */
    @Override
    public int updateImportExportTask(ImportExportTask importExportTask) {
        importExportTask.setUpdateTime(DateUtils.getNowDate());
        importExportTask.setUpdateBy(SecurityUtils.getUsername());
        return importExportTaskMapper.updateImportExportTask(importExportTask);
    }

    /**
     * 批量删除导入导出任务
     *
     * @param ids 需要删除的导入导出任务主键
     * @return 结果
     */
    @Override
    public int deleteImportExportTaskByIds(String[] ids) {
        return importExportTaskMapper.deleteImportExportTaskByIds(ids);
    }

    /**
     * 删除导入导出任务信息
     *
     * @param id 导入导出任务主键
     * @return 结果
     */
    @Override
    public int deleteImportExportTaskById(String id) {
        return importExportTaskMapper.deleteImportExportTaskById(id);
    }

    /**
     * 导出文件，并将操作记录入库
     *
     * @param importExportParamBo 进出口参数波
     * @return {@link String}
     */
    @Override
    public String exportByTempId(ImportExportParamBo importExportParamBo) {
        String id = insertExportTask(importExportParamBo);
        //状态改为等待中 0
        updateTaskStatus(id, ErrorStatus.WAITEXECUTE.getCode(), importExportParamBo);
        //异步执行
        Runnable runnable = () -> executExport(importExportParamBo, id);
        //先来的先执行
        executorService.execute(runnable);
        return id;
    }

    /**
     * 导出方法
     *
     * @param importExportParamBo 进出口参数波
     * @param id                  id
     */
    private void executExport(ImportExportParamBo importExportParamBo, String id) {
        //记录操作时间
        TimeInterval timer = DateUtil.timer();
        try {
            //状态改为执行中 1
            importExportTaskMapper.updateImportExportTask(new ImportExportTask().setId(id).setStatus(ErrorStatus.EXECUTING.getCode()));
            //设置当前线程的一个唯一标识name 以免后期手动关闭该线程
            Thread.currentThread().setName(id);
            //选择具体模板，进行相应的操作
            Long templateId = importExportParamBo.getTemplateId();
            ImportExportTemplate template = templateMapper.selectImportExportTemplateById(templateId);
            if (ObjectUtil.isEmpty(template)) {
                opError(id, "未获取到模板数据", ErrorStatus.EXCEPTION.getCode(), timer);
            } else {
                //将模板更新到数据库中
                importExportTaskMapper.updateImportExportTask(new ImportExportTask().setId(id).setTemplateId(templateId));
            }

            //获取数据库连接配置，如果为空，默认用本地的连接
            String jdbcConfigId = template.getJdbcConfigId();
            JdbcConfig jdbcConfig = jdbcConfigMapper.selectJdbcConfigById(jdbcConfigId);
            Db db = JdbcDbUtil.getDatabase(jdbcConfig);

            //更新进度状态
            updateStatusById(id, "正在获取sql数据中", XyUtils.converTime(timer.intervalSecond()));

            //获取sql数据
            Opt<List<Entity>> dataOpt = Opt.ofTry(() -> db.query(importExportParamBo.getSql()));
            if (ObjectUtil.isNotEmpty(dataOpt.getException())) {
                opError(id, "获取sql数据出错：" + dataOpt.getException().getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
            }
            List<Entity> datas = dataOpt.get();
            if (IterUtil.isEmpty(datas)) {
                opError(id, "获取sql数据为空", ErrorStatus.EXCEPTION.getCode(), timer);
            }

            //导出文件路径
            String filePath = FileUtils.getFilePathByName(XyUtils.getId32());

            updateStatusById(id, StrUtil.format("成功查询【{}】条数据，正在处理数据中", datas.size()), XyUtils.converTime(timer.intervalSecond()));
            //执行操作
            OperatyUtil.create(template, excelUtils, datas, id, timer, filePath).selectInstance().execute();

            updateStatusById(id, "数据处理成功，正在将文件上传到oss中", XyUtils.converTime(timer.intervalSecond()));
            //将本地文件上传到oss中
            OssUtils.fileUpload(filePath, filePath, true);
            //完成 记录数据库
            updateTaskEnd(id, XyUtils.converTime(timer.intervalSecond()), "导出成功", filePath);
        } catch (CustomException e) {
            System.out.println("已经处理过异常");
        } catch (ServiceException e) {
            opError(id, e.getMessage(), e.getCode(), timer);
        } catch (Exception e) {
            opError(id, "未知错误：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
    }

    /**
     * 更新状态通过id
     *
     * @param id          id
     * @param opError     消息
     * @param executeTime 执行时间
     * @return int
     */
    @Override
    public int updateStatusById(String id, String opError, String executeTime) {
        return importExportTaskMapper.updateStatusById(id, opError, executeTime);
    }

    /**
     * 插入成功后记录入库
     *
     * @return int
     */
    @Override
    public int updateTaskEnd(String id, String second, String msg, String filePath) {
        ImportExportTask task = new ImportExportTask();
        //插入成功后记录入库
        task.setId(id).setExecuteTime(second).setStatus(ErrorStatus.EXECUTOVER.getCode())
                .setOpError(msg).setUpdateTime(new Date());
        if (StrUtil.isNotBlank(filePath)) {
            task.setOpFileName(filePath);
        }
        //记录数据库
        return importExportTaskMapper.updateImportExportTask(task);
    }

    /**
     * 重新开始任务 根据任务id
     *
     * @param id id
     */
    @Override
    public void restartTaskById(String id) {
        ImportExportTask task = importExportTaskMapper.selectImportExportTaskById(id);
        restartTask(task);
    }

    /**
     * 重新开始任务
     *
     * @param task 任务
     */
    private void restartTask(ImportExportTask task) {
        String params = task.getParameters();
        String id = task.getId();
        String opType = task.getOpType();
        Long templateId = task.getTemplateId();
        final ImportExportParamBo importExportParamBo = JSONUtil.toBean(params, ImportExportParamBo.class);
        importExportParamBo.setTemplateId(templateId).setSql(task.getSqls());
        //状态改为等待中 0
        updateTaskStatus(id, ErrorStatus.WAITEXECUTE.getCode(), importExportParamBo);
        if ("export".equals(opType)) {
            //导出任务
            Runnable runnable = () -> executExport(importExportParamBo, id);
            executorService.execute(runnable);
        }
        //如果关闭系统前导入了一些数据，这里会从头开始导入，就会出现重复导入的情况
        else if ("import".equals(opType)) {
            //将oss存储的导入文件，设置到对象中
            String op_file_name = task.getOpFileName();
            InputStream fileStream = OssUtils.getFileStream(op_file_name);
            importExportParamBo.setInputStream(fileStream);
            //异步执行
            Runnable runnable = () -> importTaskExcel07SaxReader(importExportParamBo, id, op_file_name);
            executorService.execute(runnable);
        }
    }

    /**
     * 更新任务状态
     *
     * @param id                  id
     * @param status              状态
     * @param importExportParamBo 进出口参数波
     * @return int
     */
    @Override
    public int updateTaskStatus(String id, int status, ImportExportParamBo importExportParamBo) {
        if (StrUtil.isBlank(id)) {
            throw new ServiceException("任务id不能为空");
        }

        ImportExportTask task = new ImportExportTask();
        task.setStatus(status).setId(id);
        Opt.ofNullable(importExportParamBo).ifPresent(e -> {
            String parameters = beanToMapRemoveKey(importExportParamBo);
            task.setParameters(parameters);
        });
        setSystemByTemplateId(importExportParamBo, task);
        //清除消息、进度、执行时间、文件地址
        task.setOpError("").setProgress("").setExecuteTime("").setOpFileName("");
        int resNum = importExportTaskMapper.updateImportExportTask(task);
        if (ErrorStatus.ACTIVEOVER.getCode() == status) {
            //获取所有线程
            Set<Thread> threads = Thread.getAllStackTraces().keySet();
            Thread thread = threads.stream().filter(e -> id.equals(e.getName())).findFirst().orElse(null);
            Opt.ofNullable(thread).orElseThrow(() -> new ServiceException("该任务未在线程池中"));
            //关闭线程
            thread.stop();
        }
        return resNum;
    }

    /**
     * 设置系统 根据模板id
     *
     * @param importExportParamBo 进出口参数波
     * @param task                任务
     */
    private void setSystemByTemplateId(ImportExportParamBo importExportParamBo, ImportExportTask task) {
        if (ObjectUtil.isEmpty(importExportParamBo)) {
            return;
        }
        Long templateId = importExportParamBo.getTemplateId();
        JdbcConfig jdbcConfig = getJdbcByTemplateId(templateId);
        Opt.ofNullable(jdbcConfig).ifPresent(e -> task.setSystem(jdbcConfig.getDatabaseName()));
    }

    /**
     * 封装异常时插入异常消息(异常结束)
     *
     * @param id     数据id
     * @param msg    异常消息
     * @param status 异常类型   -1异常 -2超时 -3主动终止 0等待执行 1执行中 2执行完成
     * @param timer  计时器
     */
    @Override
    public void opError(String id, String msg, Integer status, TimeInterval timer) {
        log.error(msg);
        ImportExportTask task = new ImportExportTask();
        task.setId(id).setOpError(msg).setStatus(status).setExecuteTime(XyUtils.converTime(timer.intervalSecond())).setUpdateTime(new Date());
        importExportTaskMapper.updateImportExportTask(task);
        throw new CustomException(msg);
    }

    /**
     * 将文件内容导入到数据库中
     *
     * @param importData 导入数据
     * @return {@link String}
     */
    @Override
    public String importByFile(ImportExportParamBo importData) {
        String fileName = importData.getFile().getOriginalFilename();
        String fileType = FileUtils.getFileType(fileName);
        importData.setFileName(fileName).setFileType(fileType);
        //插入导入任务
        String id = insertTaskImport(importData);
        if (ObjectUtil.isEmpty(importData.getFile())) {
            opError(id, "导入文件为空", ErrorStatus.EXCEPTION.getCode(), DateUtil.timer());
        }
        String ossPath = ExcelStants.EXCEL_PATH + fileName;
        try {
            InputStream is = importData.getFile().getInputStream();
            //文件保存在本地，上传oss后会关闭流，导致后面拿不到文件流
            FileWriter writer = new FileWriter(ossPath);
            writer.writeFromStream(is, false);
            //将文件存储在oss中 ，以免项目重启时，可以再次执行该任务
            OssUtils.fileUpload(ossPath, ossPath, false);
            updateImportExportTask(new ImportExportTask().setId(id).setOpFileName(ossPath));
            //将文件流设置到对象中，防止异步时文件找不到
            importData.setInputStream(FileUtil.getInputStream(ossPath));
        } catch (IOException e) {
            opError(id, "文件转化为InputStream失败", ErrorStatus.EXCEPTION.getCode(), DateUtil.timer());
        }
        //状态改为正在执行 1
        updateTaskStatus(id, ErrorStatus.EXECUTING.getCode(), importData);
        Runnable runnable = () -> importTaskExcel07SaxReader(importData, id, ossPath);
        executorService.execute(runnable);
        return id;
    }

    /**
     * 导入任务 大量数据，读取每一条数据，按1000条进行导入
     *
     * @param importData 导入数据
     * @param id         id
     * @param ossPath    oss路径
     */
    public void importTaskExcel07SaxReader(ImportExportParamBo importData, String id, String ossPath) {
        //记录操作时间
        TimeInterval timer = DateUtil.timer();
        try {
            //状态改为执行中 1
            updateImportExportTask(new ImportExportTask().setId(id).setStatus(ErrorStatus.EXECUTING.getCode()));
            //设置当前线程的一个唯一标识name  以免后期手动关闭该线程
            Thread.currentThread().setName(id);
            //将title和column设置到对象中
            readUtils.setTitleColumn(importData, importData.getTemplateId());
            //执行导入方法
            readUtils.importTaskExcel07SaxReader(importData, id, timer);
            //插入成功后记录入库
            updateTaskEnd(id, XyUtils.converTime(timer.intervalSecond()), "导入成功", null);
            //删除保存在本地的文件
            FileUtil.del(ossPath);
        } catch (CustomException e) {
            System.out.println("已经处理过异常");
        } catch (ServiceException e) {
            opError(id, e.getMessage(), e.getCode(), timer);
        } catch (Exception e) {
            opError(id, "数据入库失败：" + e.getMessage(), ErrorStatus.EXCEPTION.getCode(), timer);
        }
    }

    /**
     * 插入任务导入
     *
     * @param importExportParamBo 进出口参数波
     */
    public String insertTaskImport(ImportExportParamBo importExportParamBo) {
        String id = XyUtils.getId32();
        String parameters = beanToMapRemoveKey(importExportParamBo);
        ImportExportTask task = new ImportExportTask();
        task.setId(id).setStatus(ErrorStatus.WAITEXECUTE.getCode()).setOpType("import").setOpAsync(1)
                .setSystem(importExportParamBo.getSystem()).setOpFileType(importExportParamBo.getFileType())
                .setParameters(parameters).setTemplateId(importExportParamBo.getTemplateId());
        task.setCreateTime(new Date());
        task.setCreateBy(SecurityUtils.getUsername());
        setSystemByTemplateId(importExportParamBo, task);
        importExportTaskMapper.insertImportExportTask(task);
        return id;
    }

    /**
     * 根据任务id 下载已完成好的文件
     *
     * @param id       id
     * @param fileName 文件名称
     * @param response 响应
     * @return {@link R}
     */
    @Override
    public R downlodaFileById(String id, String fileName, HttpServletResponse response) {
        ImportExportTask task = importExportTaskMapper.selectImportExportTaskById(id);
        if (ObjectUtil.isEmpty(task)) {
            return R.fail("任务不存在");
        }
        Integer status = task.getStatus();
        if (ErrorStatus.EXECUTOVER.getCode() != status) {
            return R.fail("任务未完成或异常");
        }
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        String opFileName = task.getOpFileName();
        //下载文件
        String filePath = OssUtils.downloadFileNow(opFileName);
        String fileType = FileUtils.getFileType(FileUtil.getName(opFileName));
        if (StrUtil.isNotBlank(fileName)) {
            fileName = FileUtils.getFileNameRomveSuffix(fileName) + "." + fileType;
        } else if (StrUtil.isNotBlank(task.getDownloadFileName())) {
            fileName = task.getDownloadFileName() + "." + fileType;
        } else {
            fileName = FileUtil.getName(opFileName);
        }
        try {
            //文件编码
            FileUtils.setAttachmentResponseHeader(response, fileName);
            FileUtils.writeBytes(filePath, response.getOutputStream());
            //删除本地文件
            FileUtil.del(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.ok("下载成功");
    }

    /**
     * 更新进展
     *
     * @param id             id
     * @param rate           率
     * @param unit           单位
     * @param time 间隔第二
     * @return int
     */
    @Override
    public int updateProgress(String id, long rate, String unit, String time) {
        ImportExportTask task = new ImportExportTask();
        task.setId(id).setProgress(rate + unit).setExecuteTime(time).setUpdateTime(new Date());
        return importExportTaskMapper.updateImportExportTask(task);
    }

    /**
     * 插入导出任务
     *
     * @param importExportParamBo 进出口参数波
     * @return {@link String}
     */
    private String insertExportTask(ImportExportParamBo importExportParamBo) {
        String id = XyUtils.getId32();
        String parameters = beanToMapRemoveKey(importExportParamBo);

        ImportExportTask task = new ImportExportTask();
        task.setId(id).setSqls(importExportParamBo.getSql()).setStatus(ErrorStatus.WAITEXECUTE.getCode())
                .setOpType("export").setOpAsync(1).setSystem(importExportParamBo.getSystem())
                .setParameters(parameters).setTemplateId(importExportParamBo.getTemplateId())
                .setDownloadFileName(importExportParamBo.getDownloadFileName());
        task.setCreateTime(new Date());
        String createBy = importExportParamBo.getCreateBy();
        if (StrUtil.isNotBlank(createBy)) {
            task.setCreateBy(createBy);
        } else {
            task.setCreateBy(SecurityUtils.getUsername());
        }

        setSystemByTemplateId(importExportParamBo, task);
        importExportTaskMapper.insertImportExportTask(task);
        return id;
    }

    /**
     * 实体类转map并删除file和流
     *
     * @param importExportParamBo 进出口参数波
     * @return {@link String}
     */
    private String beanToMapRemoveKey(ImportExportParamBo importExportParamBo) {
        String jsonStr = "";
        try {
            Map<String, Object> res = BeanUtil.beanToMap(importExportParamBo, false, true);
            res.remove("file");
            res.remove("inputStream");
            jsonStr = JSONUtil.toJsonStr(res);
        } catch (Exception e) {
            throw new ServiceException(ErrorStatus.EXCEPTION.getCode(), "参数转json出错：" + e.getMessage());
        }
        return jsonStr;
    }

    /**
     * 根据模板id获取jdbc配置
     *
     * @param templateId 模板id
     * @return {@link JdbcConfig}
     */
    @Override
    public JdbcConfig getJdbcByTemplateId(Long templateId) {
        //根据模板id查询jdbc配置
        ImportExportTemplate template = templateMapper.selectImportExportTemplateById(templateId);
        JdbcConfig jdbcConfig = Opt.ofNullable(template).map(e -> {
            String jdbcConfigId = template.getJdbcConfigId();
            return jdbcConfigMapper.selectJdbcConfigById(jdbcConfigId);
        }).orElse(null);
        return jdbcConfig;
    }

    /**
     * 获取正在执行和未执行的任务
     *
     * @return {@link List}<{@link ImportExportTask}>
     */
    public List<ImportExportTask> getNotExecute() {
        List<Integer> status = ListUtil.of(ErrorStatus.WAITEXECUTE.getCode(), ErrorStatus.EXECUTING.getCode());
        String join = ArrayUtil.join(status.toArray(), ",", "'", "'");
        return importExportTaskMapper.getNotExecute(join);
    }
}
